﻿#region MIT
/*
Engine Of Evermore (http://code.google.com/p/engineofevermore/)
Copyright (c) 2008, 2009 Michael Woerister

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#endregion

using System.Collections.Generic;
using System.Linq;
using EngineOfEvermore.Editor.SceneEditor;
using EngineOfEvermore.Scene;
using EngineOfEvermore.Common;
using System.Diagnostics;

namespace EngineOfEvermore.Editor.SceneTools
{
    enum SelectionMode
    {
        Single,
        Toggle,
        Add
    }

    class SelectionHandler : ISelectionHandler
    {
        private SceneObject[] _previouslyClickedItems = new SceneObject[] { };
        private int _previouslyClickedIndex;
        private readonly ISelectionCommandAdapter _commandAdapter;
        private SelectionMode _mode = SelectionMode.Single;

        public IEnumerable<SceneObject> Selection
        {
            get { return _commandAdapter.Selection; }
        }

        public SelectionMode Mode
        {
            get { return _mode; }
            set
            {
                _mode = value;
            }
        }

        public SelectionHandler( ISelectionCommandAdapter selectionCommandAdapter )
        {
            _commandAdapter = selectionCommandAdapter;
        }

        public void HandlePointSelection( IEnumerable<SceneObject> items )
        {
            switch ( this.Mode )
            {
                case SelectionMode.Single:
                    _handleSingleModePointSelection( items );
                    break;

                case SelectionMode.Toggle:
                    _handleToggleModePointSelection( items );
                    break;

                case SelectionMode.Add:
                    _commandAdapter.AddToSelection( items );
                    break;
            }
        }

        public void HandleAreaSelection( IEnumerable<SceneObject> items )
        {
            items = items.ToArray();

            switch ( this.Mode )
            {
                case SelectionMode.Single:
                    _commandAdapter.SetSelection( items );
                    break;

                case SelectionMode.Toggle:
                    _toggleItemSelection( items );
                    break;

                case SelectionMode.Add:
                    _commandAdapter.AddToSelection( items );
                    break;
            }
        }

        private void _handleToggleModePointSelection( IEnumerable<SceneObject> items )
        {
            switch ( items.Count() )
            {
                case 0:
                    // Do nothing
                    return;

                case 1:
                    _toggleItemSelection( items.Single() );
                    return;

                default:
                    if ( items.IsEquivalentTo( _previouslyClickedItems ) )
                    {
                        var currentItem = _previouslyClickedItems[_previouslyClickedIndex];
                        
                        _nextClickedIndex();

                        var nextItem = _previouslyClickedItems[_previouslyClickedIndex];
                        _toggleItemSelection( new SceneObject[] { currentItem, nextItem } );
                    }
                    else
                    {
                        _previouslyClickedIndex = 0;
                        _previouslyClickedItems = items.ToArray();
                        _toggleItemSelection( items.First() );
                    }
                    break;
            }
        }

        private void _handleSingleModePointSelection( IEnumerable<SceneObject> items )
        {
            if ( !items.Any() )
            {
                if ( _commandAdapter.Selection.Any() )
                {
                    _commandAdapter.ClearSelection();
                }
                
                _resetPreviouslyClickedItems();
                return;
            }

            if ( items.IsEquivalentTo( _previouslyClickedItems ) )
            {
                _previouslyClickedIndex = ( _previouslyClickedIndex + 1 ) % items.Count();
            }
            else
            {
                _previouslyClickedIndex = 0;
                _previouslyClickedItems = items.ToArray();
            }

            var newSelection = new SceneObject[] { _previouslyClickedItems[_previouslyClickedIndex] };
            _commandAdapter.SetSelection( newSelection );
        }

        private void _resetPreviouslyClickedItems()
        {
            _previouslyClickedItems = new SceneObject[] { };
            _previouslyClickedIndex = 0;
        }

        private void _nextClickedIndex()
        {
            _previouslyClickedIndex += 1;
            _previouslyClickedIndex %= _previouslyClickedItems.Length;
        }

        private void _toggleItemSelection( SceneObject item )
        {
            if ( _commandAdapter.Selection.Contains( item ) )
            {
                _commandAdapter.RemoveFromSelection( new SceneObject[] { item } );
            }
            else
            {
                _commandAdapter.AddToSelection( new SceneObject[] { item } );
            }
        }
        
        private void _toggleItemSelection( IEnumerable<SceneObject> items )
        {
            var newSelection = new List<SceneObject>( _commandAdapter.Selection );
            
            foreach ( var item in items )
            {
                if ( _commandAdapter.Selection.Contains( item ) )
                {
                    newSelection.Remove( item );
                }
                else
                {
                    newSelection.Add( item );
                }
            }
            
            Debug.Assert( newSelection.IsEquivalentTo( newSelection.Distinct() ) );
            
            _commandAdapter.SetSelection( newSelection );
        }
    }

    #if ENGINE_OF_EVERMORE_ENABLE_UNIT_TESTS
    namespace Test
    {
        using NUnit.Framework;

        [TestFixture]
        public class SelectionHandlerFixture
        {
            SelectionHandler _handler;
            ISceneSelection _selection;

            [SetUp]
            public void SetUp()
            {
                _selection = new SceneSelection();
                var commandStack = new EngineOfEvermore.Editor.Common.Command.CommandStack();
                var commandStackAdapter = new SelectionCommandAdapter( _selection, commandStack );
                _handler = new SelectionHandler( commandStackAdapter );
            }

            [Test]
            public void TestHandlerSelection()
            {
                Assert.That( _handler.Selection, Is.Empty );
            }

            [Test]
            public void TestPointSelectionNoItems()
            {
                _handler.HandlePointSelection( new List<SceneObject>() );
                Assert.That( _handler.Selection, Is.Empty );
            }

            [Test]
            public void TestPointSelectionOneItem()
            {
                var items = new SceneObject[] { new SceneObject() };
                _handler.HandlePointSelection( items );

                Assert.That( _handler.Selection.Single(), Is.SameAs( items[0] ) );
            }

            [Test]
            public void TestPointSelectionOneItemAlreadyContained()
            {
                var items = new SceneObject[] { new SceneObject() };

                _handler.HandlePointSelection( items );
                _handler.HandlePointSelection( items );

                Assert.That( _handler.Selection.Single(), Is.SameAs( items[0] ) );
            }

            [Test]
            public void TestPointSelectionMultipleItems()
            {
                List<SceneObject> selection = new List<SceneObject>();
                var items = new SceneObject[] { new SceneObject(), new SceneObject(),
                    new SceneObject() };

                int CYCLES = 3;

                for ( int i = 0; i < items.Length * CYCLES; ++i )
                {
                    _handler.HandlePointSelection( items );
                    Assert.That( _handler.Selection.Count(), Is.EqualTo( 1 ) );
                    selection.Add( _handler.Selection.Single() );
                }

                var selectionBins = new List<SceneObject>[items.Length];
                for ( int i = 0; i < selectionBins.Length; ++i )
                    selectionBins[i] = new List<SceneObject>();

                for ( int i = 0; i < items.Length * CYCLES; ++i )
                {
                    int binIndex = i % CYCLES;
                    selectionBins[binIndex].Add( selection[i] );
                }

                Assert.That( selectionBins.All( bin => bin.Distinct().Count() == 1 ) );
                Assert.That( selectionBins.Select( bin => bin.First() ).Distinct().Count(),
                    Is.EqualTo( items.Length ) );
            }

            [Test]
            public void TestToggleMode()
            {
                _handler.Mode = SelectionMode.Toggle;
                Assert.That( _handler.Mode, Is.EqualTo( SelectionMode.Toggle ) );
            }

            [Test]
            public void TestThatSingleModeIsDefault()
            {
                Assert.That( _handler.Mode, Is.EqualTo( SelectionMode.Single ) );
            }

            [Test]
            public void TestToggleModeNoItemSelected()
            {
                _handler.Mode = SelectionMode.Toggle;
                _handler.HandlePointSelection( new SceneObject[] { } );

                Assert.That( _handler.Selection, Is.Empty );
            }

            [Test]
            public void TestToggleModeOneItemSelected()
            {
                SceneObject[] items = new SceneObject[] { new SceneObject() };

                _handler.Mode = SelectionMode.Toggle;
                _handler.HandlePointSelection( items );

                Assert.That( _handler.Selection, Is.EquivalentTo( items ) );
            }

            [Test]
            public void TestToggleModeOneItemSelectedTwice()
            {
                SceneObject[] items = new SceneObject[] { new SceneObject() };

                _handler.Mode = SelectionMode.Toggle;
                _handler.HandlePointSelection( items );
                _handler.HandlePointSelection( items );

                Assert.That( _handler.Selection, Is.Empty );
            }

            [Test]
            public void TestToggleModeOneItemSelectedThreeTimes()
            {
                SceneObject[] items = new SceneObject[] { new SceneObject() };

                _handler.Mode = SelectionMode.Toggle;
                _handler.HandlePointSelection( items );
                _handler.HandlePointSelection( items );
                _handler.HandlePointSelection( items );

                Assert.That( _handler.Selection, Is.EquivalentTo( items ) );
            }

            [Test]
            public void TestToggleModeMultipleItemsOneSelected()
            {
                SceneObject[] items = new SceneObject[] { new SceneObject(), new SceneObject(),
                    new SceneObject() };
                SceneObject[] singleItem = new SceneObject[] { items[0] };

                _selection.Add( items );

                Assert.That( _handler.Selection, Is.EquivalentTo( items ) );

                _handler.Mode = SelectionMode.Toggle;
                _handler.HandlePointSelection( singleItem );

                Assert.That( _handler.Selection, Has.No.Member( singleItem[0] ) );
                Assert.That( _handler.Selection, Has.Member( items[1] ) );
                Assert.That( _handler.Selection, Has.Member( items[2] ) );

                _handler.HandlePointSelection( singleItem );

                Assert.That( _handler.Selection, Has.Member( singleItem[0] ) );
                Assert.That( _handler.Selection, Has.Member( items[1] ) );
                Assert.That( _handler.Selection, Has.Member( items[2] ) );

                _handler.HandlePointSelection( singleItem );

                Assert.That( _handler.Selection, Has.No.Member( singleItem[0] ) );
                Assert.That( _handler.Selection, Has.Member( items[1] ) );
                Assert.That( _handler.Selection, Has.Member( items[2] ) );
            }

            [Test]
            public void TestToggleSelectMultiple()
            {
                SceneObject[] allItems = new SceneObject[] { 
                    new SceneObject(), 
                    new SceneObject(),
                    new SceneObject(),
                    new SceneObject(),
                    new SceneObject()
                };

                SceneObject[] selectionItems = new SceneObject[] { 
                    allItems[0],
                    allItems[1],
                    allItems[2]
                };

                _selection.Add( allItems );

                _handler.Mode = SelectionMode.Toggle;

                Assert.That( _handler.Selection, Is.EquivalentTo( allItems ) );

                _handler.HandlePointSelection( selectionItems );

                Assert.That( _handler.Selection, Has.No.Member( selectionItems[0] ) );
                Assert.That( _handler.Selection, Has.Member( selectionItems[1] ) );
                Assert.That( _handler.Selection, Has.Member( selectionItems[2] ) );
                Assert.That( _handler.Selection, Has.Member( allItems[3] ) );
                Assert.That( _handler.Selection, Has.Member( allItems[4] ) );

                _handler.HandlePointSelection( selectionItems );

                Assert.That( _handler.Selection, Has.Member( selectionItems[0] ) );
                Assert.That( _handler.Selection, Has.No.Member( selectionItems[1] ) );
                Assert.That( _handler.Selection, Has.Member( selectionItems[2] ) );
                Assert.That( _handler.Selection, Has.Member( allItems[3] ) );
                Assert.That( _handler.Selection, Has.Member( allItems[4] ) );

                _handler.HandlePointSelection( selectionItems );

                Assert.That( _handler.Selection, Has.Member( selectionItems[0] ) );
                Assert.That( _handler.Selection, Has.Member( selectionItems[1] ) );
                Assert.That( _handler.Selection, Has.No.Member( selectionItems[2] ) );
                Assert.That( _handler.Selection, Has.Member( allItems[3] ) );
                Assert.That( _handler.Selection, Has.Member( allItems[4] ) );

                _handler.HandlePointSelection( selectionItems );

                Assert.That( _handler.Selection, Has.No.Member( selectionItems[0] ) );
                Assert.That( _handler.Selection, Has.Member( selectionItems[1] ) );
                Assert.That( _handler.Selection, Has.Member( selectionItems[2] ) );
                Assert.That( _handler.Selection, Has.Member( allItems[3] ) );
                Assert.That( _handler.Selection, Has.Member( allItems[4] ) );
            }

            [Test]
            public void TestSingleModeAreaSelection()
            {
                var items = new SceneObject[] {
                    new SceneObject(),
                    new SceneObject(), 
                    new SceneObject() 
                };

                _handler.Mode = SelectionMode.Single;
                _handler.HandleAreaSelection( items );

                Assert.That( _handler.Selection, Is.EquivalentTo( items ) );
            }

            [Test]
            public void TestSingleModeAreaSelectionTwoTimes()
            {
                var items1 = new SceneObject[] {
                    new SceneObject(),
                    new SceneObject(), 
                    new SceneObject() 
                };

                var items2 = new SceneObject[] {
                    items1[0],
                    new SceneObject(), 
                    new SceneObject() 
                };

                _handler.Mode = SelectionMode.Single;
                _handler.HandleAreaSelection( items1 );

                Assert.That( _handler.Selection, Is.EquivalentTo( items1 ) );

                _handler.HandleAreaSelection( items2 );
                Assert.That( _handler.Selection, Is.EquivalentTo( items2 ) );
            }

            [Test]
            public void TestToggleModeAreaSelection()
            {
                var allItems = new SceneObject[] { 
                    new SceneObject(), 
                    new SceneObject(), 
                    new SceneObject() 
                };

                var subSet = new SceneObject[] { allItems[0], allItems[1] };

                _selection.Add( allItems );

                _handler.Mode = SelectionMode.Toggle;
                _handler.HandleAreaSelection( subSet );

                Assert.That( _handler.Selection, Has.No.Member( subSet[0] ) );
                Assert.That( _handler.Selection, Has.No.Member( subSet[1] ) );
                Assert.That( _handler.Selection, Has.Member( allItems[2] ) );
            }

            [Test]
            public void TestAddMode()
            {
                _handler.Mode = SelectionMode.Add;

                Assert.That( _handler.Mode, Is.EqualTo( SelectionMode.Add ) );
            }

            [Test]
            public void TestAddModePointSelectionOnce()
            {
                _handler.Mode = SelectionMode.Add;

                SceneObject[] items = new SceneObject[] { new SceneObject(), new SceneObject() };

                _handler.HandlePointSelection( items );

                Assert.That( _handler.Selection, Is.EquivalentTo( items ) );
            }

            [Test]
            public void TestAddModePointSelectionTwice()
            {
                _handler.Mode = SelectionMode.Add;

                SceneObject[] items = new SceneObject[] { new SceneObject(), new SceneObject() };

                _handler.HandlePointSelection( items );
                Assert.That( _handler.Selection, Is.EquivalentTo( items ) );

                _handler.HandlePointSelection( items );
                Assert.That( _handler.Selection, Is.EquivalentTo( items ) );
            }

            [Test]
            public void TestAddModePointSelectionOverlapping()
            {
                _handler.Mode = SelectionMode.Add;

                var allItems = new SceneObject[] { 
                    new SceneObject(),
                    new SceneObject(), 
                    new SceneObject() };

                var items1 = new SceneObject[] { allItems[0], allItems[1] };
                var items2 = new SceneObject[] { allItems[1], allItems[2] };

                _handler.HandlePointSelection( items1 );
                Assert.That( _handler.Selection, Is.EquivalentTo( items1 ) );

                _handler.HandlePointSelection( items2 );
                Assert.That( _handler.Selection, Is.EquivalentTo( allItems ) );
            }

            [Test]
            public void TestAddModeAreaSelectionSimple()
            {
                _handler.Mode = SelectionMode.Add;

                var allItems = new SceneObject[] { 
                    new SceneObject(),
                    new SceneObject(), 
                    new SceneObject() };

                _handler.HandleAreaSelection( allItems );
                Assert.That( _handler.Selection, Is.EquivalentTo( allItems ) );

                _handler.HandleAreaSelection( allItems );
                Assert.That( _handler.Selection, Is.EquivalentTo( allItems ) );
            }

            [Test]
            public void TestAddModeAreaSelectionOverlapping()
            {
                _handler.Mode = SelectionMode.Add;

                var allItems = new SceneObject[] { 
                    new SceneObject(),
                    new SceneObject(), 
                    new SceneObject() };

                var items1 = new SceneObject[] { allItems[0], allItems[1] };
                var items2 = new SceneObject[] { allItems[1], allItems[2] };

                _handler.HandleAreaSelection( items1 );
                Assert.That( _handler.Selection, Is.EquivalentTo( items1 ) );

                _handler.HandleAreaSelection( items2 );
                Assert.That( _handler.Selection, Is.EquivalentTo( allItems ) );
            }
        }
    }
    #endif
}
